﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.CompilerServices;

using alib;
using alib.Debugging;
using alib.Enumerable;
using alib.String;

using agree.configuration;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Required property init:
	///		GlbCache
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed partial class TypeUtils : has_grammar_base, alib.Hashing.IIndexedHash<String>
	{
		public TypeUtils(IRtParent parent)
			: base(parent)
		{
		}

		public new GlbCache GlbCache
		{
			get { return base.glbc; }
			set
			{
				base.GlbCache = value;
				this.type_arr = g.bcpo.type_arr;
			}
		}

		Type[] type_arr;

		public int c_structures_shared;

		public bool IsTopId(Edge e)
		{
			return (e.FlagsId & Edge.Flag.IdResolve) == 0;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Various Edge creation functions from throughout history.
		/// Some auto-mark based on the bareness of the type
		/// Create an edge with the specified type, coreference status, and mark.
		/// If mark is zero but a mark is required, auto-mark based on the bareness of the type
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge CreateEdge(Type t, int m, bool f_coref)
		{
			Debug.Assert(t != null && t.bcpo == this.bcpo);

			Edge.Flag f = t.EdgeFlag;
			if (f_coref)
				f |= Edge.Flag.Coreference;
			ValidateEdgeFlags(new Edge(f, m));
			return new Edge(f, m);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Type GetEdgeType(Edge.Flag f)
		{
			Debug.Assert(f != Edge.Flag.Bottom);
			return f.IsStringFamilyType() ? td.ΔString : type_arr[(int)(f & Edge.Flag.MultiIdMask)];
		}

		[Conditional("DEBUG")]
		public void ValidateTypeFlag(Edge.Flag f)
		{
			if (type_arr == null)
				return;
			Type t = GetEdgeType(f);
			if (((f & Edge.Flag.EtmNonBareType) != 0) != ((t.m_flags & Instance.Flags.HasAppropriateFeatures) != 0))
				throw new Exception();
		}

		[Conditional("DEBUG")]
		public void ValidateEdgeFlags(Edge e)
		{
			ValidateTypeFlag(e.FlagsId);
			if (((e.FlagsId & Edge.Flag.NeedMark) == 0) != (e.Mark == 0))
				throw new Exception();
		}

		public bool TryGetTypeFlag(String s_type, out Edge.Flag f)
		{
			Type t;
			if (!td.TryGetType(s_type, out t))
			{
				f = 0;
				return false;
			}
			f = t.EdgeFlag;
			return true;
		}

		public Type UnifyTypes(String t0, String t1)
		{
			return UnifyTypesFull(td[t0], td[t1]);
		}

		/// <summary> Consider other options for highly spun code </summary>
		public Type GetGlbOfMany(IEnumerable<Type> _ie)
		{
			IEnumerator<Type> ie = _ie.GetEnumerator();
			Type t_cur, t = td.ΔTop;
			while (ie.MoveNext())
			{
				if ((t_cur = ie.Current) != td.ΔTop && t_cur != t)
					if ((t = UnifyTypesFull(t, t_cur)) == null)
						return null;
			}
			return t;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// *top* and coreferenced ok
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public bool CanUnifyToString(Edge.Flag f)
		{
			/// because this unary function tests compatibility with the (bare) *string* type and not string values,
			/// we can summarily exclude all leaf types (which therefore excludes all string values).
			if ((f & Edge.Flag.EtmLeafType) != 0)
				return false;

			/// verify that *string* has the correct, fixed flag value
			Debug.Assert(!f.IsStringFamilyType() || f.IsTheStringType());

			f &= Edge.Flag.IdResolve;
			/// String values are already EtmLeafType, but we also want to exclude EtmString itself, which is not.
			return f == 0 || f == Edge.Flag.EtmString || _can_unify_to_string(f);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		bool _can_unify_to_string(Edge.Flag f)
		{
			Debug.Assert(f != 0 && !f.IsStringFamilyType());
			return glbc.get_glb((Edge.Flag)td.ΔString._id, f) >= 0;
		}
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Type UnifyTypesFull(Type t0, Type t1)
		{
			//Debug.Assert(t0 != strings.ΔString && t1 != strings.ΔString);

			if (t0.IsTop || t0 == t1)
				return t1;
			if (t1.IsTop)
				return t0;

			Edge.Flag f0, f1;
			if (((f0 = t0.EdgeFlag) & (f1 = t1.EdgeFlag)) >= Edge.Flag.EtmLeafType)
				return null;

			Edge.Flag f = f1 < f0 ? UnifyTypesHiLo(f1, f0) : UnifyTypesHiLo(f0, f1);
			return f.IsStringFamilyType() ? td.ΔString : type_arr[(int)(f & Edge.Flag.MultiIdMask)];
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Edge.Flag UnifyTypesFull(Edge.Flag f0, Edge.Flag f1)
		{
			if ((f1 &= ~Edge.Flag.Coreference) == (f0 &= ~Edge.Flag.Coreference))
				return f0;

			if ((f0 & f1) >= Edge.Flag.EtmLeafType)
				return Edge.Flag.Bottom;

			return f1 < f0 ? (f1 == 0 ? f0 : UnifyTypesHiLo(f1, f0)) : (f0 == 0 ? f1 : UnifyTypesHiLo(f0, f1));
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public bool QuickTypeSubsumeCheck(Edge.Flag super, Edge.Flag sub)
		{
			if (super == 0 || super == sub)
				return true;
			if ((super & sub) >= Edge.Flag.EtmLeafType)
				return false;
			return (super < sub && UnifyTypesHiLo(super, sub) == sub);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public Edge.Flag UnifyTypesHiLo(Edge.Flag hi, Edge.Flag lo)
		{
			Debug.Assert(hi > 0 && lo > hi);
			Debug.Assert((hi & lo & Edge.Flag.EtmLeafType) == 0);

			/// ERG: 'anti_string' and 'non_string' are ordinary types which inherit from 'string'
			if (((hi | lo) & Edge.Flag.EtmString) != 0)
				return hi.IsTheStringType() || _can_unify_to_string(hi) ? lo : Edge.Flag.Bottom;

			if ((lo & Edge.Flag.EtmLeafType) != 0)
				return type_arr[(int)(hi & Edge.Flag.MultiIdMask)].bit_vec[type_arr[(int)(lo & Edge.Flag.MultiIdMask)].bit_num] ?
							lo :
							Edge.Flag.Bottom;

			return glbc.get_glb(hi, lo);
		}

		public static Edge CreateIntegerEdge(int value)
		{
			Debug.Assert(value >= 0 && value <= (int)Edge.Flag.subid_mask);
			return new Edge(EdgeFlag.CreateIntegerValue(value), 0);
		}

		const String vartypes = "?hexuidp";
		public static String GetSkolemTypeString(Edge.Flag v)
		{
			Debug.Assert(v.IsSkolemValue());
			return vartypes[(int)(v & Edge.Flag.subtype_id_mask) >> 24].ToString();
		}
		public static String GetSkolemString(Edge.Flag skolem_value)
		{
			return GetSkolemTypeString(skolem_value) + (int)(skolem_value & Edge.Flag.subid_mask);
		}

		public static ulong GetSkolemBitMask(Edge.Flag skolem_value)
		{
			return (ulong)1 << ((int)(skolem_value & Edge.Flag.subid_mask) - 1);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Create a string value edge for a unique string value
		/// </summary>
		/// <remarks>
		/// note: empty string is distinct from unconstrained string type
		///			s1 := *top* & [ X *top* ].
		///			s2 := s1 & [ X "foo" ].
		///			s3 := s1 & [ X "" ].
		///			s4 := s2 & s3.
		/// </remarks>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public Edge CreateStringEdge(String s)
		{
			int sid = td.strings.Add(s);
			Debug.Assert(sid >= 0 && sid <= (int)Edge.Flag.subid_mask);
			return new Edge(Edge.Flag.EtmStringValue | (Edge.Flag)sid, 0);
		}

		//[MethodImpl(MethodImplOptions.AggressiveInlining)]
		//public bool IsStringValue(Edge.Flag f)
		//{
		//	bool b = f.IsStringValue();// && (f & Edge.Flag.MultiIdMask) != 0;
		//	if (!b && !f.IsNonStringType())
		//		Debug.Assert((f & Edge.Flag.MultiIdMask) == 0);
		//	return b;
		//}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public String GetStringValue(Edge.Flag f)
		{
			if (!f.IsStringValue())
				return null;
			int sid = (int)(f & Edge.Flag.MultiIdMask);
			return sid == 0 ? String.Empty : td.strings[sid];
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public String GetStringFamilyValue(Edge.Flag f)
		{
			if (f.IsTheStringType())
				return td.ΔString.Name;
			if (f.IsStringValue())
				return td.strings[(int)(f & Edge.Flag.MultiIdMask)];
			if (f.IsSkolemValue())
				return GetSkolemString(f);
			if (f.IsIntegerValue())
				return f.GetIntegerValue().ToString();
#if DEBUG
			if (f.IsStringFamilyType())
				throw new Exception();
#endif
			return null;
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public String TypeNameOrStringValue(Edge.Flag f)
		{
#if DEBUG
			if (f == Edge.Flag.Bottom)
				return "*bottom*";
#endif
			var s = GetStringFamilyValue(f);
			if (s != null)
				return s;

			int mid = (int)(f & Edge.Flag.MultiIdMask);
			return mid == 0 ? td.ΔTop.Name : type_arr[mid].Name;
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public String TypeNameOrStringValueDisplay(Edge.Flag f)
		{
			String s = TypeNameOrStringValue(f);
			if (f.IsIntegerValue())
				s = "(" + s + ")";
			else if (f.IsStringValue())
				s = "\"" + s + "\"";
			return s;
		}

		/// <summary> currently does not handle converting skolem constants such as "h3" from string form to Edge.Flag </summary>
		public Edge.Flag StringToTypeOrStringValue(String s)
		{
			Type t;
			if (s[0] == '\"')
			{
				if (s[s.Length - 1] != '\"')
					throw new TypeManagerException("Missing '\"' in string '{0}'", s);
				s = s.Substring(1, s.Length - 2);
			}
			else if (td.TryGetType(s, out t))
				return t.EdgeFlag;
			int sid;
			if ((sid = td.strings[s]) == -1)
				throw new TypeManagerException("'{0}' is not a string in grammar '{1}'.", s, g.Name);
			return Edge.Flag.EtmStringValue | (Edge.Flag)sid;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// IIndexedHash<String>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		int alib.Hashing.IIndexedHash<String>.this[String to_find]
		{
			get { return (int)StringToTypeOrStringValue(to_find); }
		}

		String IReadOnlyList<String>.this[int index]
		{
			get { return TypeNameOrStringValue((Edge.Flag)index); }
		}

		String[] alib.Hashing.IIndexedHash<String>.ConvertTo(IEnumerable<int> items)
		{
			String[] arr = new String[items._Count()];
			var e = items.GetEnumerator();
			int i = 0;
			while (e.MoveNext())
				arr[i++] = TypeNameOrStringValue((Edge.Flag)e.Current);
			return arr;
		}

		int[] alib.Hashing.IIndexedHash<String>.ConvertFrom(IEnumerable<String> items)
		{
			FeatureInfo fi;
			int[] arr = new int[items._Count()];
			var e = items.GetEnumerator();
			int i = 0;
			while (e.MoveNext())
				arr[i++] = (int)StringToTypeOrStringValue(e.Current);
			return arr;
		}

		int IReadOnlyCollection<String>.Count { get { return int.MaxValue; } }
		int ICollection.Count { get { return int.MaxValue; } }
		int _IList<string>.Count { get { return int.MaxValue; } }

		IEnumerator<String> IEnumerable<String>.GetEnumerator() { throw not.impl; }

		IEnumerator IEnumerable.GetEnumerator() { throw not.impl; }

		void ICollection.CopyTo(Array array, int index) { throw not.impl; }

		bool ICollection.IsSynchronized { get { return false; } }

		Object ICollection.SyncRoot { get { return this; } }
	};
}
